﻿using System;
using System.Data;
using System.Data.Common;

namespace StandardDAC.Base {
    /// <summary>
    /// Class Properties are related to Database Columns via *this* attribute class. This also deals with
    /// the base interactions that the DAC will have when using each Parameter/Columns. (Property requires
    /// public get/set). (Note: These data classes have a 1:1 relationship with Tables)
    /// </summary>
    public abstract class DBColumn : Attribute {
        #region members

        string name;//column name
        bool isPrimaryKey;//flag indicating if column is part of the primary key for current table
        bool isIdentity;//flag indicating if column is an identity column

        object dbType;//database column type (ie: SqlDbType.Int)
        object nullValue = null;//value that indicates a null value for the class variable.

        #endregion

        #region properties
        /// <summary>
        /// Column Name
        /// </summary>
        public string Name {
            get { return name; }
        }
        /// <summary>
        /// Flag indicating if this column is part of a primary key for this table
        /// </summary>
        public bool IsPrimaryKey {
            get { return isPrimaryKey; }
        }
        #endregion

        #region constructors
        /// <summary>
        /// Constructor indicating column name and the database column type (ie: SqlDbType.Int).
        /// Null Value: null. NOT a primary key
        /// </summary>
        /// <param name="name">Column Name</param>
        /// <param name="dbType">(Column's Type -- ie: SqlDbType.Int)</param>
        public DBColumn(string name, object dbType) : this(name, dbType, null, false) { }
        /// <summary>
        /// Constructor indicating column name and the database column type (ie: SqlDbType.Int) and null value.
        /// NOT a primary key
        /// </summary>
        /// <param name="name">Column Name</param>
        /// <param name="dbType">(Column's Type -- ie: SqlDbType.Int)</param>
        /// <param name="nullValue">Object's value that corresponds to DBNull.Value</param>
        public DBColumn(string name, object dbType, object nullValue) : this(name, dbType, nullValue, false) { }
        /// <summary>
        /// Constructor indicating column name, the database column type (ie: SqlDbType.Int), the null value,
        /// and flag identifying this column as being part of the primary key for this table.
        /// </summary>
        /// <param name="name">Column Name</param>
        /// <param name="dbType">(Column's Type -- ie: SqlDbType.Int)</param>
        /// <param name="nullValue">Object's value that corresponds to DBNull.Value</param>
        /// <param name="isPrimaryKey">Flag identifying this column as being part of the primary key for this table</param>
        public DBColumn(string name, object dbType, object nullValue, bool isPrimaryKey)
            : this(name, dbType, nullValue, isPrimaryKey, isPrimaryKey) {
        }
        /// <summary>
        /// Constructor indicating column name, the database column type (ie: SqlDbType.Int), the null value,
        /// and flag identifying this column as being part of the primary key for this table.
        /// </summary>
        /// <param name="name">Column Name</param>
        /// <param name="dbType">(Column's Type -- ie: SqlDbType.Int)</param>
        /// <param name="nullValue">Object's value that corresponds to DBNull.Value</param>
        /// <param name="isPrimaryKey">Flag identifying this column as being part of the primary key for this table</param>
        /// <param name="isIdentity">Flag identifying this column as having generated values (ie: no insert/update)</param>
        public DBColumn(string name, object dbType, object nullValue, bool isPrimaryKey, bool isIdentity) {
            this.name = name;
            this.dbType = dbType;
            this.isPrimaryKey = isPrimaryKey;
            this.nullValue = nullValue;
            this.isIdentity = isIdentity;
        }
        #endregion

        /// <summary>
        /// Returns flag indicating if the input value is null, w/ respect to this columns definitions of null
        /// </summary>
        /// <param name="value">input value</param>
        /// <returns></returns>
        private bool IsNull(object value) {
            return (value == null || value.Equals(this.nullValue));
        }
        /// <summary>
        /// Adds parameter for the inputted value of this column.
        /// </summary>
        /// <param name="cmd">Command to add the parameter to</param>
        /// <param name="value">Inputted value of the column</param>
        /// <param name="isCreateOrUpdate">Create procedures return the identity/primary key as an output 
        /// parameter. So we expect it to come out. If this is a READ/DELETE we're not expecting an output
        /// parameter.</param>
        /// <param name="dbtype">The connection type adapter</param>
        public void CreateParameter(DbCommand cmd, object value, bool isCreateOrUpdate, IDBTypeAdapter dbtype) {
            bool isnullflag = IsNull(value);
            DbParameter p = dbtype.AddNewParameter(dbtype.ConvertNameToParam(this.name), this.dbType, cmd);
            
            AddTypeSpecificInformation(p);

            if (isnullflag)
                p.Value = DBNull.Value;
            else
                p.Value = FormatNonNullValue(value);

            if (isCreateOrUpdate && isnullflag && (isPrimaryKey || isIdentity)) {
                p.Direction = ParameterDirection.Output;
            }
        }

        /// <summary>
        /// Does not operate on null values.
        /// When a parameter is set with a value, this can be overridden to provide any additional formatting.
        /// (ie: with connections to the AS-400 you might want to do this for a char(15) --> 
        /// return value.Length>SIZE ? value.Substring(0, SIZE) : value.PadRight(SIZE); ).
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        protected virtual object FormatNonNullValue(object value) {
            return value;
        }
        /// <summary>
        /// If there's any type specific information (ie: scale / precision / length), this procedure can be 
        /// overrided to add that data to the parameter.
        /// </summary>
        /// <param name="p"></param>
        protected virtual void AddTypeSpecificInformation(DbParameter p) {
        }
        public override string ToString() {
            return name;
        }
    }
    
}